﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace armsim
{
    public partial class Form1 : Form
    {

        public VirtualComputer Computer { get; set; }

        readonly System.Windows.Forms.Timer _timer = new System.Windows.Forms.Timer();


        public Form1(Options options)
        {
            InitializeComponent();
            this.Computer = new VirtualComputer(options.MemorySize);
            uint entryPoint;

            if (!String.IsNullOrEmpty(options.InputFile))
            {
                entryPoint = Computer.OpenNewProgram(options.InputFile);
                this.toolStripStatusLabel1.Text = String.Format("Checksum: {0}", Computer.CPU.RAM.CheckSum());
                this.toolStripStatusLabel2.Text = String.Format("Current Program: {0}", options.InputFile.Contains("\\") ? options.InputFile.Split('\\').Last() : options.InputFile);
            }
            UpdateAllViews();

            SetIcons(true, false, true, true);
            InitializeTimer();

            if (options.Exec && !String.IsNullOrEmpty(options.InputFile))
            {
                tsbPlay.PerformClick();
            }

            
        }

        // Constructs a new form 
        public Form1()
        {
            InitializeComponent();
            this.Computer = new VirtualComputer();
            SetIcons(false, false, false, false);
            InitializeTimer();
        }

        // Constructs a new form with a given file name and loads it into memory
        public Form1(String fileName)
        {
            InitializeComponent();

            this.Computer = new VirtualComputer();
            var entryPoint = Computer.OpenNewProgram(fileName);

            UpdateAllViews();

            this.toolStripStatusLabel1.Text = String.Format("Checksum: {0}", Computer.CPU.RAM.CheckSum());
            this.toolStripStatusLabel2.Text = String.Format("Current Program: {0}", fileName.Contains("\\") ? fileName.Split('\\').Last() : fileName);

            SetIcons(true, false, true, true);
            InitializeTimer();
        }

        // Constructs a new form with a given file name and memory size and loads it into memory
        public Form1(String fileName, uint memSize)
        {
            InitializeComponent();
            
            this.Computer = new VirtualComputer(memSize);
            var entryPoint = Computer.OpenNewProgram(fileName);

            UpdateAllViews();

            this.toolStripStatusLabel1.Text = String.Format("Checksum: {0}", Computer.CPU.RAM.CheckSum());
            this.toolStripStatusLabel2.Text = String.Format("Current Program: {0}", fileName.Contains("\\") ? fileName.Split('\\').Last() : fileName);

            SetIcons(true, false, true, true);
            InitializeTimer();
        }

        public Form1(uint memSize)
        {
            InitializeComponent();
            this.Computer = new VirtualComputer(memSize);
            SetIcons(false, false, false, false);
            InitializeTimer();
        }

        // Sets the timer properties
        private void InitializeTimer()
        {
            _timer.Interval = 250;
            _timer.Tick += CheckRunFinished;
        }

        // Called by the timer to check if a thread has finished or not
        private void CheckRunFinished(object sender, EventArgs e)
        {
            progressBarRun.Visible = true;

            if (Computer.IsFinishedOrCanceled())
            {
                _timer.Stop();
                progressBarRun.Visible = false;
                UpdateAllViews();

                if (Computer.IsFinished())
                {
                    SetIcons(false, false, false, true);
                }
                else if (Computer.IsCanceled())
                {
                    SetIcons(true, false, true, true);
                }

            }

        }
  
        // Opens a file dialog and if the user selects something, it looads a new program into memory
        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var dialog = new OpenFileDialog();
            dialog.Filter = @"Exectutable files (*.exe)|*.exe|All files (*.*)|*.*";
            dialog.InitialDirectory = Application.ExecutablePath;
            dialog.RestoreDirectory = true;
            var dialogResult = dialog.ShowDialog();

            if (dialogResult == DialogResult.OK)
            {
                try
                {
                    
                    var entryPoint = Computer.OpenNewProgram(dialog.FileName);
                    this.toolStripStatusLabel1.Text = String.Format("Checksum: {0}", Computer.CPU.RAM.CheckSum());
                    this.toolStripStatusLabel2.Text = String.Format("Current Program: {0}",
                                                                    dialog.FileName.Contains("\\")
                                                                        ? dialog.FileName.Split('\\').Last()
                                                                        : dialog.FileName);
                    SetIcons(true, false, true, true);
                    UpdateAllViews();
                }
                catch
                {
                    MessageBox.Show(@"Not enough memory to load program.", @"Error");
                    this.toolStripStatusLabel2.Text = @"Current Program: None";
                    SetIcons(false, false, false, false);
                }
          
                
            }
        }

        // Allows the user to jump to an address in memory and display its contents
        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                var startingAddress = Convert.ToUInt32(textBoxStartingAddress.Text, 16);
                UpdateMemoryView(startingAddress, 240);
            }
            catch 
            {
               
            }

        }
 
        // Displays the memory correctly in the memory panel
        private void UpdateMemoryView(uint startingAddress, uint count)
        {

            listBoxMemory.Items.Clear();

            var row = String.Empty;
            var numIterations = startingAddress + count;

            var i = 0;
            for (var address = startingAddress; address < numIterations; ++address)
            {
                if (i % 15 == 0)
                {
                    if (!String.IsNullOrEmpty(row))
                    {
                        listBoxMemory.Items.Add(row);
                    }
                    row = String.Empty;
                    row += String.Format("0x{0:X8}    ", address);
                }

                row += String.Format("{0:X2} ", Computer.CPU.RAM.ReadByte(address));
                ++i;
            }



        }

        // Displays the register properly in the panel
        private void UpdateRegistersView()
        {


            uint i = 0;
            for (var iter = 0; iter < 16; ++iter)
            {
                var row = listBoxRegisters.Items[iter] as String;
                listBoxRegisters.Items[iter] = String.Format("{0} 0x{1:X8}", row.Substring(0, row.IndexOf('=') + 1), Computer.CPU.Registers.ReadWord(i));

                i += 4;
            }


        }

        // Displays the current contents of disassembly
        private void UpdateDisassemblyView()
        {

            listViewDisassembler.Items.Clear();
       
            var currentPC = Computer.CPU.Registers.ReadWord(VirtualMemory.PC);

            var startingAddress = currentPC - 20;
            var endingAddress = currentPC + 28;

            for (var i = startingAddress; i < endingAddress; i += 4)
            {
                var address = String.Format("0x{0:X8}", i);
                var instruction = String.Format("0x{0:X8}", Computer.CPU.RAM.ReadWord(i));
                var assembly = InstructionFactory.GenerateInstruction(Computer.CPU.RAM.ReadWord(i)).ToAssembly(Computer.CPU);//RandomAssemblyInstruction((int)i);

                var item = listViewDisassembler.Items.Add(address);
                item.SubItems.AddRange(new[] { instruction, assembly });

                if (i == currentPC)
                {
                    item.BackColor = Color.Yellow;

                }

            }

            

        }

        // Displays the current contents of the stack (or will eventually)
        private void UpdateStackView()
        {

            stackListView.Items.Clear();
            var stackPointer = Computer.CPU.Registers.ReadWord(VirtualMemory.SP);
            var framePointer = Computer.CPU.Registers.ReadWord(VirtualMemory.FP);

            var spMemory = Computer.CPU.RAM.ReadWord(stackPointer);
            var fpMemory = Computer.CPU.RAM.ReadWord(framePointer);

            for (var i = stackPointer; i <= stackPointer + 80; i+=4)
            {
                var address = String.Format("0x{0:X8}", i);
                var data = String.Format("0x{0:X8}", Computer.CPU.RAM.ReadWord(i));
                var item = stackListView.Items.Add(address);
                item.SubItems.AddRange(new[] { data });
            }


        }

        // Updates all the views
        private void UpdateAllViews()
        {
            UpdateMemoryView(0, 240);
            UpdateRegistersView();
            UpdateDisassemblyView();
            UpdateStackView();
        }

        // Helper method that generates a random assembly instruction (used for the disassembler)
        private static String RandomAssemblyInstruction(int seed)
        {
            var rand = new Random(seed);
            var commandChooser = rand.Next(1, 9);
            string command;
            switch (commandChooser)
            {
                case 0:
                    command = String.Format("LDR R{0},[R{1}]", new Random(seed + 1).Next(0, 11), new Random(seed + 9).Next(0, 11));
                    break;
                case 1:
                    command = String.Format("STR R{0},[R{1}]", new Random(seed + 2).Next(0, 11), new Random(seed + 3).Next(0, 11));
                    break;
                case 2:
                    command = String.Format("MOV R{0}, #{1}", new Random(seed + 99).Next(0, 11), new Random(seed + -2).Next(1, 200));
                    break;
                case 3:
                    command = String.Format("ADD R{0}, R{1}, R{0}", new Random(seed + 3).Next(0, 11), new Random(seed + 6).Next(0, 11));
                    break;
                case 4:
                    command = String.Format("CMP R1, #{0}", new Random(seed + 88).Next(1, 300));
                    break;
                case 6:
                    command = String.Format("SUBS R{0}, R{1}, #{2}", new Random(seed + 10).Next(1, 11), new Random(seed + -10).Next(1, 11), new Random(seed + 77).Next(1, 300));
                    break;
                case 7:
                    command = String.Format("MUL R{0}, R{1}, R{2}", new Random(seed + 12).Next(1, 11), new Random(seed + -99).Next(1, 11), new Random(seed + 99).Next(1, 300));
                    break;
                case 8:
                    command = String.Format("AND R{0}, R{1}, #1", new Random(seed + 56).Next(1, 11), new Random(seed + 15).Next(1, 11));
                    break;
                case 9:
                    command = String.Format("ORR R{0}, R{1}, #1", new Random(seed).Next(1, 11), new Random(seed + 56).Next(1, 11));
                    break;
                default:
                    command = String.Format("ORR R{0}, R{1}, #1", new Random(seed + 55).Next(1, 11), new Random(seed + 77).Next(1, 11));
                    break;
            }

            return command;

        }

        private void tsbPlay_Click(object sender, EventArgs e)
        {
            _timer.Start();
            SetIcons(false, true, false, false);
            new Thread(Computer.Run).Start();
        }

        // Tells a running thread to stop running. Sets the icons and updates the views
        private void tsbStop_Click(object sender, EventArgs e)
        {
            Computer.Cancel();
            //SetIcons(true, false, true, true);
            
        }

        // Lets the computer run a single fetch, decode, execute cycle.
        // Updates all the panels and sets the icons accordingly
        private void tsbSet_Click(object sender, EventArgs e)
        {
            if (!Computer.Step())
            {
                SetIcons(false, false, false, true);
            }
            UpdateAllViews();
         
        }

        // Reload a file into memory and displays the file and the check sum into the toolstrip
        private void tsbReset_Click(object sender, EventArgs e)
        {
            var fname = Computer.GetCurrentFilePath();

            var entryPoint = Computer.OpenNewProgram(fname);

            

            this.toolStripStatusLabel1.Text = String.Format("Checksum: {0}", Computer.CPU.RAM.CheckSum());
            this.toolStripStatusLabel2.Text = String.Format("Current Program: {0}", fname.Contains("\\") ? fname.Split('\\').Last() : fname);

            UpdateAllViews();
            SetIcons(true, false, true, true);

        }

        // Sets the four icons' enabled property to the specified values for each one
        private void SetIcons(bool run, bool stop, bool step, bool reset)
        {
            tsbPlay.Enabled = run;
            tsbStop.Enabled = stop;
            tsbSet.Enabled = step;
            tsbReset.Enabled = reset;
        }

        // Enables the Tracing for the computer and sets the enabled feature of the menu items accordingly
        private void enableTraceToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Computer.Tracer.EnableTracing();
            enableTraceToolStripMenuItem.Enabled = false;
            disableTraceToolStripMenuItem.Enabled = true;
        }

        // Disables the Tracing for the computer and sets the enabled feature of the menu items accordingly
        private void disableTraceToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Computer.Tracer.DisableTracing();
            enableTraceToolStripMenuItem.Enabled = true;
            disableTraceToolStripMenuItem.Enabled = false;
        }

        // Connects the hotkeys to the events. Only runs the event if its icon is enabled
        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            switch (keyData)
            {
                case (Keys.Control | Keys.O):
                    if (openToolStripMenuItem.Enabled)
                    {
                        openToolStripMenuItem.PerformClick();
                    }
                    break;
                case (Keys.F5):
                    if (tsbPlay.Enabled)
                    {
                        tsbPlay.PerformClick();
                    }
                    break;
                case Keys.F10:
                    if (tsbSet.Enabled)
                    {
                        tsbSet.PerformClick();
                    }
                    break;
                case (Keys.Control | Keys.B):
                    if (tsbStop.Enabled)
                    {
                        tsbStop.PerformClick();
                    }
                    break;
                case (Keys.Control | Keys.T):
                    if (enableTraceToolStripMenuItem.Enabled)
                    {
                        enableTraceToolStripMenuItem.PerformClick(); 
                    }
                    else if (disableTraceToolStripMenuItem.Enabled)
                    {
                        disableTraceToolStripMenuItem.PerformClick();
                      
                    }
                    break;
                case (Keys.Control | Keys.R):
                    if (tsbReset.Enabled)
                    {
                        tsbReset.PerformClick();
                    }
                    break;
            }


            return base.ProcessCmdKey(ref msg, keyData);
        }

        // Exits the application completely. No prompt is given if the computer is not running a program
        // if there is a program running, prompts the user with a confirmation box
        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (!_timer.Enabled)
            {
                Application.Exit();
            }
            else
            {
                var result = MessageBox.Show(@"You are currently running a program. Are you sure you want to exit?", @"Quit Confirmation", MessageBoxButtons.YesNo);

                if (result == DialogResult.Yes)
                {
                    Computer.Cancel();
                    Application.Exit();
                }
            }
        }

    }
}
